﻿// Deep Earth is a community project available under the Microsoft Public License (Ms-PL)
// Code is provided as is and with no warranty – Use at your own risk
// View the project and the latest code at http://DeepEarth.codeplex.com/

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using DeepEarth.Client.MapControl.Events;
using DeepEarth.Client.MapControl.Geometries;

namespace DeepEarth.Client.MapControl.Layers
{
    /// <summary>
    /// <para>
    /// A Raster Tile Layer for the map
    /// Creates a MultiScaleImage control to the map control and links up to a TileSource.
    /// The layer has one TileSource and can contain many Overlays (also TileSource)
    /// </para>
    /// <example>
    /// 
    /// <code title="Change the Maps's TileLayer's source to VE Road">   
    /// //Assume that your DeepEarth.Client.MapControl control is called "map"
    /// map.BaseLayer.Source = new VeTileSource(VeMapModes.VeRoad); 
    /// </code>
    /// </example>
    /// </summary>
    public class TileLayer : Grid, ILayer
    {
        private readonly MultiScaleImage _msi;
        private TileSource _tileSource;
        private double _lastViewWidth;

        protected Map _MapInstance;
        protected bool _IsVisible = true;

        /// <summary>
        /// A collection of additional TileSource's to be overlaid on the base TileSource
        /// Unfortunately there is no opacity support for these.
        /// </summary>
        public ObservableCollection<ITileSource> Overlays { get; set; }


        public string Id { get; set; }

        /// <summary>
        /// A Raster Tile Layer for the map
        /// </summary>
        /// <param name="map">Instance of the map in which to create the layer</param>
        public TileLayer(Map map)
        {
            _MapInstance = map;

            _msi = new MultiScaleImage { CacheMode = new BitmapCache(), UseSprings = false, IsHitTestVisible = false, BlurFactor = 0.5D };

            _msi.Loaded += _Msi_Loaded;
            _msi.LayoutUpdated += (o, e) => CheckViewBounds();
            _msi.MotionFinished += (o, e) => _MapInstance.Events.ViewChangedEnded(_MapInstance, new MapEventArgs());
            _msi.ViewportChanged += _Msi_ViewportChanged;

            _lastViewWidth = _msi.ViewportWidth;

            Overlays = new ObservableCollection<ITileSource>();

            //This null TileSource allows the MSI to size itself correctly and earlier
            _msi.Source = new TileSource(map);
            Children.Add(_msi);
        }

        void _Msi_ViewportChanged(object sender, RoutedEventArgs e)
        {
            CheckViewBounds();
            CheckForZoomStart();
            _MapInstance.Events.ViewChanged(_MapInstance, new MapEventArgs());

        }

        private void CheckForZoomStart()
        {
            var width = Math.Round(_msi.ViewportWidth, 7);
            if (_lastViewWidth != width)
            {
                _MapInstance.Events.ZoomStarted(_MapInstance, new MapEventArgs());
                _lastViewWidth = width;
            }
        }

        void _Msi_Loaded(object sender, RoutedEventArgs e)
        {
            IsLoaded = true;
            _MapInstance.Events.Loaded(_MapInstance, new MapEventArgs());
        }

        /// <summary>
        /// The actual base TileSource for the Layer.
        /// </summary>
        public TileSource Source
        {
            get
            {
                return (TileSource)_msi.Source;
            }
            set
            {
                _tileSource = value;

                if (value == null)
                {
                    _msi.Source = null;
                }
                else
                {
                    MapInstance.CrossFadeTransistion();
                    Background = new SolidColorBrush(_tileSource.TileColor);

                    if (_tileSource.IsInitialized)
                    {
                        _msi.Source = _tileSource;
                        MapInstance.Events.TileSourceChanged(MapInstance, new MapEventArgs());
                    }
                    else
                    {
                        _tileSource.InitializeCompleted += (o, ev) =>
                        {
                            _msi.Source = _tileSource;
                            MapInstance.Events.TileSourceChanged(MapInstance, new MapEventArgs());
                        };
                    }

                }
            }
        }

        /// <summary>
        /// Has the TileLayer been Loaded?
        /// Until the TileLayer is loaded some functionality will not be available, 
        /// consider subscribing to the Map.Event.MapLoaded event.
        /// </summary>
        public bool IsLoaded { get; set; }

        public MultiScaleImage Msi { get { return _msi; } }

        private void CheckViewBounds()
        {
            var currZoom = _MapInstance.ZoomLevel;
            var geoPoint = _MapInstance.CoordHelper.LogicalToGeo(_MapInstance.LogicalCenter);
            if (Source != null)
            {
                if ((Source.IsValidZoomLevel(currZoom) && Source.IsValidGeoPoint(geoPoint)) == false)
                {
                    var bestZoom = Source.GetValidatedZoomLevel(currZoom);
                    var bestCenter = Source.GetValidatedGeoPoint(geoPoint);
                    _MapInstance.SetViewCenter(bestCenter, bestZoom);
                }
            }
        }


        public bool IsVisible
        {
            get { return _IsVisible; }
            set
            {
                if (_IsVisible != value)
                {
                    _IsVisible = value;
                    OnIsVisibleChanged();
                }
            }
        }

        public Map MapInstance
        {
            get { return _MapInstance ?? (_MapInstance = Map.GetMapInstance(this)); }
            set
            {
                if (_MapInstance != null && !ReferenceEquals(_MapInstance, value))
                {
                    throw new NotSupportedException();
                }
                _MapInstance = value;
            }
        }

        protected virtual void OnIsVisibleChanged()
        {

        }
    }
}